--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════#
--║   GRINDING MOBJ THINKERS   ║ Stuff related to objects grinding. "MobjThinkers" are called from trackers if not a player!
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════#
local RAIL = GS_RAILS if RAIL.blockload return end --
local RAILTYPE = GS_RAILS_TYPES
local DF = RAILTYPE[0] --The default table for railtyppes.
local DFSTATS = GS_RAILS_SKINS["Default"] --The default character S_SKIN.

--Returns how far "s" is on the rail.
RAIL.RAILPROGRESS = function(s, GS, rail, VERTRAIL)
	local SPEED = (GS.railspeed or 0)+((rail.GSconveyor) and rail.GSconveyor<<16 or 0)
	local m = rail.GSnextrail
	local LENGTH = rail.GSrailXYlength or 0
	if (SPEED >= 0)
		if rail.GSnextrail and rail.GSnextrail.GSrailXYlength
			LENGTH = $+(rail.GSnextrail.GSrailXYlength/2)
		else
			LENGTH  = $+(1<<16)
		end
		return min(LENGTH, FixedHypot(s.x-rail.x, s.y-rail.y)), LENGTH  --
	else
		if rail.GSprevrail and rail.GSprevrail.GSrailXYlength
			LENGTH = $+(rail.GSprevrail.GSrailXYlength/2)
		else
			LENGTH  = $+(1<<16)	
		end
		return min(LENGTH, FixedHypot(s.x-m.x, s.y-m.y)), LENGTH --
	end
end

--Checks if we should even be on the rail or not. GS and CONVEYOR are optional, but recommended.
--Returns XYlength if valid, otherwise 0.
RAIL.ValidGrind = function(GS, rail, CONVEYOR)
	if rail and rail.valid and rail.GSrailXYlength	
		if CONVEYOR==nil 
			CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
		end
		local NEXT = rail.GSnextrail 
		if (((GS.railspeed or 0)+CONVEYOR) <0) 
			NEXT = rail.GSprevrail
		end
		if NEXT and NEXT.valid
			return rail.GSrailXYlength or 1 --
		end
	end return 0 --
end

--Check if a rail is a vertical one. GS can simply just be for checking, ALTNEXT forces the next rail to use for reference.
RAIL.VertRail = function(rail, GS, ALTNEXT)
	if not (rail and rail.valid) return end --No rail.

	local RAILLIMIT = rail.GSrailXYlength or 0
	if not (RAILLIMIT <= 34<<16) return end --Too horizontally long to be a vertical rail.
	
	local NEXT = rail.GSnextrail if (ALTNEXT) NEXT = ALTNEXT end
	if not (NEXT and NEXT.valid) return end --Rail doesn't go anywhere and is probably an endpoint.
	
	local STARTZ, DESTZ = rail.z, NEXT.z
	local ZLIMIT = max(STARTZ, DESTZ)-min(STARTZ, DESTZ)
	local XYLIMIT = max(3, RAILLIMIT*3/2)
	if (ZLIMIT > RAILLIMIT)
		if GS=="check" or (ALTNEXT)
			return STARTZ,DESTZ,ZLIMIT,NEXT
		elseif GS
			NEXT = rail.GSprevrail 
			if (GS.railspeed+((rail.GSconveyor and not (GS and (GS.grinding or 0)<=2)) and rail.GSconveyor<<16 or 0) >= 0) 
				NEXT = rail.GSnextrail
			end
			if NEXT and NEXT.valid
				return STARTZ or 1,DESTZ,ZLIMIT,NEXT
			end
		end
	end
end

--Check if we're not BARELY missing a spring/booster on the rail's edge while grinding.
RAIL.ScanCollide = function(s, GS, COLLIDESKIP, SPEED, XYDIST)
	if GS.justtouchedrailthing return end --Just touched one.
	if (s.GScollide) and not (COLLIDESKIP) return end --Already colliding.
	
	local rail = GS.myrail if not (rail and rail.valid and rail.GSnextrail)  print("\x81 Lolnah") return end --
	if SPEED==nil
		SPEED = (GS.railspeed or 0) + ((rail.GSconveyor) and rail.GSconveyor<<16 or 0)
	end
	if XYDIST==nil
		XYDIST = (48*max(FRACUNIT, s.scale)) + abs(SPEED) + abs(s.radius) --Distance to check for.
	end
	local NEXT = (SPEED >= 0) and rail.GSnextrail or rail.GSprevrail
	local RETURNVALUE, TYPE = nil
	
	searchBlockmap("objects", function(ref, t)
		if not (t and t.valid and t.GSrailobject and not t.player) return nil end --
		
		TYPE = RAIL.AttachList[t.type]
		if not ((t.flags & MF_SPRING) or TYPE=="booster" or TYPE=="damage") return nil end --
		local TODIST = FixedHypot(t.x-s.x, t.y-s.y)
		
		if (TODIST > ((XYDIST)+4<<16)) return nil end --  print("\x85"+"Too-far:\x82"+TODIST/FRACUNIT+"\x80 XYDIST:\x82"+XYDIST/FRACUNIT)
		
		if (abs(s.z-t.z) > ((XYDIST/3)+99*s.scale) ) return nil end -- print("\x85"+"Too high/low")
		
		if not (t.GSrailobject==rail or t.GSrailobject==NEXT) return nil end -- print("\x85"+"Not connected to our rail.")
		
		if TYPE=="booster" or (t.flags & MF_SPRING)
			if (P_TryMove(s, t.x, t.y, true)) s.z = t.z end
		end
		
		s.GScollide,GS.justtouchedrailthing = t,6
		RETURNVALUE = true
	--	print("\x81 SWAP RAIL COLLIDE! ["+RETURNVALUE+"]")
		return true --
		
	end, s, s.x-XYDIST, s.x+XYDIST, s.y-XYDIST, s.y+XYDIST)
	if GS.justtouchedrailthing and s.GScollide
		RAIL.AttachedCollission(s, rail, GS)
		s.GScollide = nil
		if GS --In some extremely rare cases, GS might be nil now.
			GS.exittic = nil 
		end
		return RETURNVALUE --See where the next tic takes us!
	end
end

--Return vertical Z position depending on railspeed+conveyor.
RAIL.SetVertZ = function(rail, SPEED, s)
	local VALUE = rail.z or 0
	if not (rail.GSnextrail and rail.GSnextrail.valid)
		if (s) VALUE = s.z end
	else
		local RESULT = (rail.GSnextrail.z < rail.z) and -1 or 1 
		if ((SPEED or 0) < 0) RESULT = $*-1 end
		if RESULT==1
			VALUE = max(rail.z, rail.GSnextrail.z)
		else
			VALUE = min(rail.z, rail.GSnextrail.z)
		end
	end
	if rail
		if rail.GSVertRailBotZ!=nil
			VALUE  = max(rail.GSVertRailBotZ, $)
		end
		if rail.GSVertRailTopZ!=nil
			VALUE  = min(rail.GSVertRailTopZ, $)
		end	
	end
	return VALUE --
end

--Didn't intend to support this vertical nonsense at first, but here we are.
RAIL.VerticalGrind = function(p,s,GS, CONVEYOR, REVERSE, STARTZ, DESTZ, ZDIST)
	if not (s.health) or not (GS.grinding) or GS.leftrail return end 
	
	ZDIST = ZDIST or 0
	
	local rail = GS.myrail
	local SPEED = (GS.railspeed or 0)+(CONVEYOR or 0)
	local DISTMOVE = SPEED
	local NEXT = rail.GSnextrail if (SPEED < 0) NEXT = rail.GSprevrail end
	
	local RSTATS = GS.MyRailStats	
	local LOOP = RAIL.LearnLoopType(rail or GS.LastRail)
	local RAILANG = RAIL.ANG(rail)
	local NANG = RAIL.ANG(rail)+ANGLE_180 // if NANG==nil NANG = R_PointToAngle2(rail.x, rail.y, rail.GSnextrail.x, rail.GSnextrail.y) end
	local DANG = (rail.GSnextrail.z < rail.z) and ANGLE_180 or 0
	local OGZ, RZ = s.z, s.z
	local ZSPEED = 0
	local LOOPFLAGS = rail.GSloopflags or 0
	
	if (RSTATS.INIT < 2) --JUST got on the rail!
		RSTATS.INIT = 2
		RSTATS.falling = 0
		if rail.GSVertRailBotZ!=nil
			OGZ = max(rail.GSVertRailBotZ, $)
		end
		if rail.GSVertRailTopZ!=nil
			OGZ = min(rail.GSVertRailTopZ, $)
		end	
		RSTATS.LASTZ, RZ = OGZ, OGZ
		local X,Y = rail.x, rail.y
		if rail.GSVertRail==1 --If next rail is above, this is 1.
			if (SPEED >= 0)
				X,Y = rail.GSnextrail.x, rail.GSnextrail.y
			end
		elseif rail.GSVertRail==-1 --If next rail is above, this is 1.
			if (SPEED < 0)
				X,Y = rail.GSnextrail.x, rail.GSnextrail.y
			end		
		end
		P_MoveOrigin(s, X, Y, OGZ) 
		GS.railprogress = min(GS.railprogress or 0, 1)
		DISTMOVE,SPEED = $/2,$/2 --Ease into it.
	elseif RSTATS.LASTZ!=nil
		RZ = RSTATS.LASTZ
		OGZ = RSTATS.LASTZ
	end
	local RX,RY = rail.x, rail.y
	if (DANG) and (DISTMOVE) --The NEXT rail is BELOW the CURRENT one!
		if (SPEED < 0) --You're going to "prevrail", which is ABOVE you. AKA, you should be going UP unless reversing.
			DISTMOVE = abs($)*P_MobjFlip(s)
		else --if (SPEED < 0) --You're going to "NEXT" rail, which is BELOW you. AKA, you should be going DOWN unless reversing.
			DISTMOVE = -abs($)*P_MobjFlip(s)
		end
	else --The NEXT rail is ABOVE the CURRENT one.
		if (SPEED < 0) --You're going to "prevrail", which is BELOW you. AKA, you should be going DOWN unless reversing.
			DISTMOVE = -abs($)*P_MobjFlip(s)
		else --if (SPEED < 0) --You're going to "NEXT" rail, which is ABOVE you. AKA, you should be going UP unless reversing.
			DISTMOVE = abs($)*P_MobjFlip(s)
		end		
	end
	if LOOP and (LOOP.walljump)
		if RSTATS.INIT==2
			RSTATS.WJTIMER = 0
			if (abs(GS.railspeed) > 70*s.scale)
				GS.railspeed = ($<0) and -70*s.scale or 70*s.scale
			end
			GS.railspeed = $/2
			SPEED = $/2
			DISTMOVE = $/2
		else
			RSTATS.falling = 0
			RSTATS.WJTIMER = RSTATS.WJTIMER and $+1 or 1
			if (RSTATS.WJTIMER > 55)
				RSTATS.falling = 99<<16 --Time to fall.
			end
		end
	elseif rail.GSrailspeed and DISTMOVE and SPEED
		DISTMOVE = FixedDiv(FixedMul($, rail.GSrailspeed), 100)
		SPEED = FixedDiv(FixedMul($, rail.GSrailspeed), 100)
	end
	
	if (DESTZ < STARTZ)
		RZ = $+ DISTMOVE*P_MobjFlip(s)
		ZSPEED = (RZ-OGZ)*P_MobjFlip(s)
		
		if (RZ < DESTZ) and (ZSPEED < -s.scale)
			GS.railprogress = abs(ZDIST)+(6999<<16)
		elseif (RZ > STARTZ) and (ZSPEED > s.scale)
			GS.railprogress = abs(ZDIST)+(6999<<16)
		else
			GS.railprogress = abs( min(DESTZ, RZ)-max(DESTZ, RZ) )
		end
		
	else
	
		RZ = $+ DISTMOVE*P_MobjFlip(s)
		ZSPEED = (RZ-OGZ)*P_MobjFlip(s)
		
		if (RZ < min(DESTZ, STARTZ)) and (ZSPEED < -s.scale)
			GS.railprogress = abs(ZDIST)+(6999<<16)
		elseif (RZ > max(DESTZ, STARTZ)) and (ZSPEED > s.scale)
			GS.railprogress = abs(ZDIST)+(6999<<16)
		else
			GS.railprogress = max(STARTZ, RZ)-min(STARTZ, RZ)
		end
	end		
	if p
		if not ((rail.GShangrail) and not (s.eflags & MFE_VERTICALFLIP)) --Makes no sense to set balancing mechanics here.
		and not ((s.eflags & MFE_VERTICALFLIP) and (rail.GShangrail))
			GS.badbalance = false
			GS.railtilt,GS.turntoia = 0,0
			GS.perfectcorner = max(GS.perfectcorner or 0, 5)
			GS.ANGswap = nil
			if GS.flipanim or GS.flip --Can't fliptrick on completely vertical rails, duh.
				if not (LOOP and (LOOP.walljump or LOOP.anim and LOOP.anim!=0))
					GS.flipanim = min($, 2)	
				else
					GS.flipanim,GS.flip = nil
					if GS.animcall=="flipanim" GS.animcall = nil end
					s.mirrored = false
					s.spritexoffset,s.spriteyoffset = 0,0
				end
			end
		end
		if ((GS.grinding or 0) < 3) and not (LOOP)
		or LOOP and LOOP.walljump --Face the highest side of the rail.
			local P = (rail.GSnextrail and (rail.GSnextrail.z > rail.z)) and ANGLE_180 or 0
			p.drawangle = RAILANG+P
			GS.willfacerail,GS.turntoia,GS.camwait = 0,0,0
			local ANG,PLUS = RAIL.AngleDifference(RAILANG+P, s.angle, 2, ANG20*4, 2)
			if (ANG > ANG60)
				s.angle = RAILANG+P
			end
		end
		if not (LOOP) or LOOP.anim==0 
		or ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
			GS.nocam = max(GS.nocam or 0, 3)
		end
	elseif not (LOOP) and not (s.target and s.target.player) --For non-players.
		local P = (rail.GSnextrail and (rail.GSnextrail.z > rail.z)) and ANGLE_180 or 0
		s.angle = RAILANG+P	
		GS.willfacerail = 0
	end
	if rail.GSVertRail==1 --If next rail is above, this is 1.
		if (SPEED >= 0)
			RX,RY = rail.GSnextrail.x, rail.GSnextrail.y
		end
	elseif rail.GSVertRail==-1 --If next rail is above, this is 1.
		if (SPEED < 0)
			RX,RY = rail.GSnextrail.x, rail.GSnextrail.y
		end		
	end
	
	P_MoveOrigin(s, RX, RY, RZ)--Teleport, Mehrio.
	local USEANG = NANG+DANG
	if rail.GSforceUDMFang!=nil
		USEANG = rail.GSforceUDMFang+ANGLE_180
	end
	if not (rail.GSinvisiblerail) and not (rail.flags2 & MF2_DONTDRAW) and (rail.sprite)
		s.spritexoffset,s.spriteyoffset = 0,0
		local DIST = 38*s.scale
		if p and LOOP and LOOP.walljump
			DIST = 23*s.scale
		--	if p p.drawangle = USEANG end --For netgames, mostly.
		end
		for i = 1,3
			P_TryMove(s, s.x-FixedMul(DIST, cos(USEANG)), s.y-FixedMul(DIST, sin(USEANG)), true)
			DIST = $/2
		end
	elseif LOOP and GS.grinding and not (LOOP.anim==0) //and (LOOP.walljump)
		local DIST = ((netgame==true) and 10 or 5)*s.scale
		for i = 1,(netgame==true) and 3 or 6 --Shoot forwards a bit on invisible walljump rails to make life easier for mappers.
			P_TryMove(s, s.x+FixedMul(DIST, cos(USEANG)), s.y+FixedMul(DIST, sin(USEANG)), true)
			DIST = $/2
		end
	end
	RSTATS.LASTZ = RZ --Remember where we're meant to be so SRB2 clipping doesn't interfere.
	RSTATS.VERTRAIL = 2

	if not (rail) or not GS.grinding
		RAIL.ExitRail(s, true)
	elseif not (GS.railprogress > ZDIST) --Swap to the next rail if there is one!
	and not (GS.FORCEKICK)
		if LOOP and LOOP.walljump
			s.momx,s.momy = 0,0
		elseif p
			P_InstaThrust(s, p.drawangle, 99)
		else
			P_InstaThrust(s, RAILANG, 99)
		end
		RSTATS.ZSPEED = ZSPEED
		if ((RSTATS.falling or 0) > 15)
			RAIL.ExitRail(s, true) --No rail. Convert momentum and go!	
			if GS
				GS.cannotgrind = max((GS.cannotgrind or 0)+8, 24)
			end
		elseif RSTATS.ZSPEED!=nil
			local ADD = 0
			if LOOP and LOOP.walljump
				if (RSTATS.ZSPEED < 0) 
					ADD = -s.scale/4
				else
					ADD = s.scale/2
					if (abs(GS.railspeed or 0) > 18*s.scale) --OK, slow down, jeez.
						GS.railspeed = FixedDiv(FixedMul($,46),47)
						RSTATS.ZSPEED = FixedDiv(FixedMul($,46),47)
					end					
				end
			else
				if (RSTATS.ZSPEED < 0) 
					ADD = (abs(GS.railspeed or 0) < 35*s.scale) and -(s.scale*5/3) or -(s.scale*4/3)
				else
					ADD = (abs(GS.railspeed or 0) < 37*s.scale) and s.scale/2 or s.scale*2/3 
				end
			end
			if ADD
				GS.railspeed = $-(ADD*RAIL.GrindFlip(s, rail)) --Replace conventional slope physics like so.
			end
		end
		return true --
	else
		local XNEXT = rail if (SPEED < 0) XNEXT = rail.GSnextrail end	
		
		if not rail
		or not (NEXT and NEXT.valid) or (NEXT.flags2 & MF2_DONTDRAW) or NEXT.GSrailXYlength==nil
		or ((SPEED < 0) or (ZSPEED < 0)) and (not rail.GSprevrail or not rail.GSprevrail.valid or rail.GSprevrail.GSrailXYlength==nil)
		or (GS.FORCEKICK)
			RAIL.ExitRail(s, true) --No rail. Convert momentum and go!	
		else
			if (SPEED < 0)
				if not rail.GSprevrail or not rail.GSprevrail.valid or rail.GSprevrail.GSrailXYlength==nil
				or not NEXT.GSprevrail or not NEXT.GSprevrail.valid or NEXT.GSprevrail.GSrailXYlength==nil
					RAIL.ExitRail(s, true) return true --No rail. Convert momentum and go!
				end
			end
			if (RSTATS.INIT > 2)
				local NEWRAILANG = RAIL.ANG(NEXT)
			/*	if NEXT and NEXT.GSforceUDMFang!=nil
					NEXT = NEXT.GSforceUDMFang
				end */
				GS.prevrailang = GS.grinddirection
				if p 
					p.drawangle = NEWRAILANG+GS.raildirection
				else
					s.angle = NEWRAILANG+GS.raildirection
				end
				P_MoveOrigin(s, XNEXT.x,  XNEXT.y, RAIL.SetVertZ(GS.myrail, SPEED, s))
				
				GS.myrail, GS.railprogress = NEXT,0 --Attach to the new rail.
				RSTATS.INIT = 0 if RSTATS.ZSPEED RSTATS.ZSPEED = $/99 end
			end
		end
	end return true --
end

--This is the main grinding code, meant for all mobjs, not just players!
RAIL.GrindRail = function(s, rail)
	local GS = s.GSgrind --Setup missing vars so I don't have to do a billion nil checks.
	if GS==nil or GS.cannotgrind==nil or GS.grinding==1 RAIL.SetupMobjRailVars(s) GS=s.GSgrind end
	if not (s.health)
		if not (GS.leftrail) RAIL.ExitRail(s, true, true) end return "exit" -- Dead men grind no rails.	
	end
	if not (rail and rail.valid)
		if not (GS.myrail and GS.myrail.valid)
			GS.myrail = nil 
			if not GS.leftrail
				RAIL.ExitRail(s, true, true) return "exit" --
			end
		end
		rail = GS.myrail --The rail we're on. Vital info we really can't miss.
	end
	local p = s.player --This is NOT a player-specific thinker, only a shortcut! It may be invalid.

	if GS.leftrail --Just left a rail one to two tics ago. Abort thinker!
		if not p GS.leftrail = max($-1,0) end --PlayerThinker does this too!
		return "justleft" --
	end
	
	local TYPE = RAILTYPE[RAIL.LearnRailType(rail)]
	local HANGRAIL = RAIL.HangRail(s, rail) --Determine if we're on a hangrail!
	local RAILANG = RAIL.ANG(rail)
	local CURVEANG = RAILANG
	local SKIN = (p) and GS_RAILS_SKINS[s.skin] or nil
	local STATS = (SKIN) and SKIN["S_SKIN"] or nil

	local GRINDING = GS.grinding or 0 --Uses LAST tic's GS.grinding value!
	local LOOP = RAIL.LearnLoopType(rail)
	local JUMPFLAGS,LOOPFLAGS,GRINDFLAGS,CONVEYOR = 0,0,0

	GS.grinding = $+1 --Count upwards to keep track of how long we're grinding.
	GS.cannotgrind = max($, 2) --Don't bother with rail collision after we're attached.
	if rail and rail.valid
		CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
		JUMPFLAGS,LOOPFLAGS,GRINDFLAGS = rail.GSjumpflags or 0, rail.GSloopflags or 0, rail.GSgrindflags or 0
		if P_IsObjectOnGround(s)
			s.z = $+(s.scale*P_MobjFlip(s))		
		end
		if not (p and (p.pflags & PF_STARTJUMP) and (p.cmd.buttons & BT_JUMP))
		and not (rail.GSVertRail and not GS.grinding)
			s.momz = -((P_GetMobjGravity(s) or 0)*9/8)
		end
		s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ|MFE_JUSTSTEPPEDDOWN)		
	end

	--Calculate rail grinding speed!
	if (GRINDFLAGS & 524288)
		if CONVEYOR
			GS.railspeed = (CONVEYOR<0) and -1 or 1
		else
			GS.railspeed = ((GS.railspeed or 0)<0) and -1 or 1
		end
	elseif GS.railminspeed or not p --Non-players usually get a minimum value of 12, but you can force any number on anything.
		if GS.railminspeed == nil
			GS.railminspeed = 12
		end
		if GS.railminspeed --If it's not 0, apply!
			local MIN = GS.railminspeed*s.scale
			if (MIN > abs(GS.railspeed)+abs(GS.railboost or 0)+abs(CONVEYOR or 0))
				GS.railspeed = (GS.railspeed < 0) and -MIN or MIN
			end
		end
	end

	--Receive a speed penalty for landing on the rail with a weird momentum angle. Allign yourself properly!
	if GS.landangle != nil --Usually given on initial rail collission.
		RAIL.MobjToRailAngle(s, rail, GS.landangle, true)
		if (s.state == S_PLAY_PAIN) and p --Face the direction the player will be expecting.
			p.powers[pw_flashing] = min($,102)
			RAIL.MobjToRailAngle(s, rail, s.angle, false)
		end
		GS.landangle = nil
	end

	--Lazy hack to invert railboost, the equivalant of P_Thrust for railgrinding.
	if GS.railboost and (GS.railspeed < 0)
		GS.railboost = -$ --GS.raildirectioncheck = true --Check next frame for better accuracy.
	end

	--This is our non-sloped rail speed! We don't lose speed in SRB2 on flat rail surfaces.
	GS.railspeed = $+GS.railboost 
	GS.railboost = 0 --Use this value instead of P_Thrust. Mess with GS.railspeed directly to mimmick P_InstaThrust
	
	local REVERSE = 1 --Are we reversing on the rail? Only possible with slopes, normally.
	local GAINING = false
	local RSTATS = GS.MyRailStats
	
	--Now apply slope physics if the rail's sloped!
	if rail.GSrailZDelta and not (RSTATS and RSTATS.VERTRAIL) and not (STATS and STATS.SLOPEPHYSICS==false)
		local ZDELTA = rail.GSrailZDelta
		if (abs(ZDELTA) >= ANG1*9) --Diminishing returns for sanity's sake.
			local ZDELT = abs(ZDELTA)
			local ANG = ZDELT/ANG1
			if (ANG >= 39)
				ZDELT = min(ANG1*39, 	FixedDiv(FixedMul($, 4), 7) )
			elseif (ANG >= 30) 
				ZDELT = min(ANG30, 		FixedDiv(FixedMul($, 12), 16) )
			elseif (ANG >= 22)
				ZDELT = min(ANG1*22, 	FixedDiv(FixedMul($, 25), 30) )
			elseif (ANG >= 16)
				ZDELT = min(ANG1*16, 	FixedDiv(FixedMul($, 50), 57) )
			elseif (ANG >= 12)
				ZDELT = min(ANG1*12, 	FixedDiv(FixedMul($, 50), 55) )
			else
				ZDELT = min(ANG1*9, 	FixedDiv(FixedMul($, 25), 26) )
			end
			ZDELTA = ZDELT*((rail.GSrailZDelta < 0) and -1 or 1)
		end
		local EXTRASPEED = (ZDELTA/1000)*P_MobjFlip(s)
		if EXTRASPEED and not (GRINDFLAGS & 524288)
			REVERSE = (RAIL.AngleDifference(RAIL.ANG(rail),p and p.drawangle or s.angle, 2, ANGLE_90,2) > ANGLE_45) and 1 or -1
			REVERSE = (GS.railspeed<0) and -$ or $	
			if (GS.railspeed < 0) and (GS.railspeed+EXTRASPEED < GS.railspeed)
			or (GS.railspeed >= 0) and (GS.railspeed+EXTRASPEED >= GS.railspeed)
				GAINING = true
			end
			
			local MAX = DFSTATS.MAXSPEED*s.scale if STATS and STATS.MAXSPEED!=nil MAX = STATS.MAXSPEED*s.scale end
			local CSPEED = abs(GS.railspeed)			
			if GS.zboost --Initial boost/slowdown strength depends on your landing momentum. Lasts 2 to 3 tics.
				local BOOST = GS.zboost/FRACUNIT
				local NEXT = GS.railspeed+FixedDiv(FixedMul(EXTRASPEED, 50), max(4, min(53, 53-BOOST)) )
				if not (abs(NEXT) > MAX)
					GS.railspeed = NEXT CSPEED = abs(GS.railspeed)
				end
			end
			if (CSPEED > 20*s.scale) --The boost from slopespeed needs to be stronger the faster you're going to feel impactful
				if (CSPEED > 40*s.scale)
					if (CSPEED > max(200*s.scale, MAX)) and (GAINING) and (s.skin!="sms")
						EXTRASPEED = $/9 --Enough of you and shenanigans!
					else
						if (CSPEED > 99*s.scale) and GS.crouchgrind and GAINING==true
							EXTRASPEED = $/4
						else
							EXTRASPEED = (CSPEED > 70*s.scale) and $/5 or $/6
						end
					end
				else
					EXTRASPEED = $/5
				end
			else
				EXTRASPEED = (CSPEED > 10*s.scale) and $*2/9 or $/4
			end	
			
			if GAINING==false
				if (CSPEED > max(180*s.scale, MAX))
					EXTRASPEED = $*3/2
				end
			else
				if MAX and (CSPEED > MAX)
					if REVERSE==1 or (STATS and STATS.MINSPEED)
						EXTRASPEED = 0 --No more to be gained!
					end
				end
			end
			if EXTRASPEED --Now adjust for crouch slope bonuses.
				if GS.flipanim and not (GS.zboost)
					if (GS.flipanim!=1) --You'll lose less speed but gain less too mid-jump. 
						EXTRASPEED = (GAINING==false) and $ or $/2
					elseif GAINING==false
						EXTRASPEED = (GAINING==false) and $*2 or $*6/5 --Lose & gain more speed upon landing on a hill.
					end
				else
					if GS.crouchgrind --Crouching gives more favorable slope momentum so long as you're well balanced!
					and (GS.perfectcorner or not GS.badbalance)
						if GAINING==false
							EXTRASPEED = $*5/6 --Lose 17% less speed to slopes.
						else
							EXTRASPEED = $*7/5 --Gain 40% more slopespeed
						end
					end
					if (CSPEED < 37*s.scale) and GAINING==false --Some mercy for slow characters.
						EXTRASPEED = (CSPEED > 24*s.scale) and $*3/4 or $*2/3
					end
				end
				if GAINING==true and REVERSE==1 --Initial stat-based multiplier.
					local RMAX = min(240*s.scale, MAX) if RMAX==0 RMAX = 240*s.scale end 
					if (CSPEED < RMAX/2)
						EXTRASPEED = (CSPEED < max(50*s.scale, RMAX/3)) and $*2 or $*3/2
					end
				end
				if STATS and STATS.SLOPEMULT and EXTRASPEED --Multiplier/Reducer of slope physics from S_SKIN?
					local MULT = STATS.SLOPEMULT or 0
					EXTRASPEED = FixedDiv(FixedMul($, 100+MULT), 100)
				end			
				if not (GAINING==true and MAX and (CSPEED > MAX))
				or REVERSE==-1 and (CSPEED < 150*s.scale)
					GS.railspeed = $+EXTRASPEED --FINALLY apply that shit.
				end
			end
		end
	end
	GS.reversing = REVERSE or 1 --Convenience feature.

	--What's the direction we're grinding towards?
	if (GS.railspeed+CONVEYOR < 0)
		GS.grinddirection = RAILANG
	else
		GS.grinddirection = RAILANG+ANGLE_180
	end
	if not GS.grinding return end --
		
	local Rspeed = abs(GS.railspeed or 1) --Convenience version Railspeed that's always positive and never 0. Ignores CONVEYOR
	
	--Rail Grinding start, sounds and visual effects! Sound gets louder depending on speed. 
	local rvolume = min( 255, max(160, (max(Rspeed, s.scale)/s.scale)*4 ) )	
	if GS.grinding == 2 --Our first tic of grinding! GS.GRINDING==2
		s.momx,s.momy,s.momz,s.pmomz = 0,0,0,0
		GS.grindsfx,GS.grindsfx2 = 0,0

		--We just landed on it, so snap to the appropriate point on the rail
		if not rail.GSVertRail --Regular rail snap.
			local snapX,snapY
			if (GRINDFLAGS & 524288)
				GS.railboost,Rspeed,GS.preswitchspeed,GS.startboost = 0,0,nil,nil
				if (JUMPFLAGS & (1048576|2097152)) --Trying to make an 06 jumprope? I'mma help. Snap the player to the center!
				and rail.GSrailXYlength and (rail.GSrailXYlength < 2800<<16) and not (CONVEYOR) 
					snapX = rail.x-FixedMul(rail.GSrailXYlength/2, cos(RAILANG))
					snapY = rail.y-FixedMul(rail.GSrailXYlength/2, sin(RAILANG))
				end
				GS.ropesnap = rail
			end
			if snapX==nil
				snapX, snapY = RAIL.GetRailXYDistFromOrigin(s, rail) --Get our distance from the rail's starting position
			end
			local snapZ = RAIL.GetRailZ(rail, s, 0, nil, {X=snapX, Y=snapY})
			if p 
				p.pflags = $ & ~PF_CANCARRY	
				s.z = snapZ P_TryMove(s, snapX, snapY, true)
			end
			P_MoveOrigin(s, snapX, snapY, snapZ) --Now SNAP to the rail, where we're meant to be!
		end
		if rail.GSfaceforward --Forcibly face a certain way?
			CONVEYOR,GS.railspeed = abs($),abs($)	
			GS.reversing = 1
			if rail.GSfaceforward!=-1
				RAIL.MobjToRailAngle(s, rail, RAILANG+ANGLE_180, false)
			else
				RAIL.MobjToRailAngle(s, rail, RAILANG, false)			
			end
		end
		if GS.startboost --If your mobj spawns with this value, start grinding with this speed everytime!
			GS.railboost = $+GS.startboost
		end
		--Add rail switching speed when applicable! Speedrunners will LOVE the small boost.
		if GS.preswitchspeed
			if (Rspeed+abs(CONVEYOR) > 30*s.scale)
				GS.EaseInSpeed = 175 --Next tic, slow down a bit.
			end
			if (CONVEYOR)
				GS.zboost = nil
			elseif not (Rspeed > 75*s.scale)
				GS.railboost = $+min(3*s.scale, abs(GS.preswitchspeed/9))
			end
			GS.railswitching,GS.preswitchspeed = 0,nil --Just incase.
			if GS.zboost and (Rspeed > 24*s.scale) --Don't repeatedly get Z boosts to infinity.
				GS.zboost = $/((Rspeed<38*s.scale) and 3 or (Rspeed<50*s.scale) and 6 or (Rspeed<60*s.scale) and 12 or 48)
			end
		end
		if GS.railspeed
			local SPEEDMOD, STARTMOD = (abs(GS.railspeed)<22*s.scale) and 1 or (abs(GS.railspeed)>61*s.scale) and 3 or 2, 30
			if STATS 
				if STATS.STARTMODIFIER!=nil --Modify initial speed!	
					SPEEDMOD = max(-95, $-STATS.STARTMODIFIER)
				end
				if STATS.STARTSPEEDCONDITION!=nil --When speed modifier takes place. Default is 30.
					STARTMOD = abs(STATS.STARTSPEEDCONDITION)
				end
			end
			if (SPEEDMOD) and (abs(GS.railspeed) > (STARTMOD*s.scale))
				GS.railspeed = FixedDiv(FixedMul($, max(1, 100)), 100+SPEEDMOD)
				if GS.railboost
					GS.railboost = FixedDiv(FixedMul($, max(1, 100)), 100+SPEEDMOD) 
				end
			end
			if rail.GSrailstartcap and (abs(GS.railspeed) > rail.GSrailstartcap*s.scale)
				GS.railspeed = ($<0) and -abs(rail.GSrailstartcap)*s.scale or abs(rail.GSrailstartcap)*s.scale
			end
		end
		if not GS.silentgrind or (GS.silentgrind==2)
			GS.startSFX = true
		end
		if s.sprite==SPR_UNKN --Reset states so we won't permanently be stuck with this because a mapper forced a wrong sprite.
			states[S_PLAY_GSGRINDING]={sprite=SPR_PLAY,frame=SPR2_GRND,var1=0,var2=0,duration=3,nextstate=S_PLAY_GSGRINDING}
			states[S_PLAY_RIDE] ={sprite = SPR_PLAY,frame = SPR2_RIDE,var1 = 0,var2 = 0,duration = 4,nextstate = S_PLAY_RIDE}
			s.state = S_PLAY_FALL
		--	print("\x85"+"RESET-STATE,\x81 SPR_UNKN\x85 happened. \x88 STATESnum: "+(#states))
		end			
		if SKIN
			if SKIN["StartGrind"] and SKIN["StartGrind"](p, s, GS, rail) end
		end
		if (GRINDFLAGS & 131072) and (abs(GS.railspeed) < 12*s.scale)
			GS.railspeed = ($<0) and -12*s.scale or 12*s.scale --Don't trap our poor player with nocontrol, pls.
		end
	end
	if GS.startSFX!=nil
		if not (LOOPFLAGS & 1048576)
			local DIDFAST = false
			if (GS.silentgrind!=2) and (Rspeed > 15*s.scale) --Fast start sfx. Creates sparks.
				if GS.startSFX!="noland" and (TYPE.nolandingspark!=true) --Initial landing sparks.
					local COLOR,COLORIZED = (TYPE.sparkscolor) or 0, (TYPE.sparkscolorized) and true or false
					RAIL.LandingSpark(s, COLOR, COLORIZED, HANGRAIL, (TYPE.sparkscale) or FRACUNIT/2)
					RAIL.RailSparks(s, 8, P_RandomRange(8,20), P_RandomRange(-30,30), HANGRAIL)
				end
				if (Rspeed > 40*s.scale) and not GS.ropesnap --Play the speeding sound?
					if (HANGRAIL!=true) or (TYPE.nohangrailSFX==true)
						if not (TYPE.nofastSFX==true or TYPE.faststartSFX==sfx_none)
							DIDFAST = true
							S_StartSoundAtVolume(s, TYPE.faststartSFX or DF.faststartSFX, rvolume)
							GS.grindsfx2 = TYPE.fastSFXinit or DF.fastSFXinit //19
						end
					end
				end
			end
			if (HANGRAIL!=true) or (TYPE.nohangrailSFX==true)
				if (TYPE.grindstartSFX!=sfx_none) and not (DIDFAST==true and TYPE.nostackSFX==true) and not GS.ropesnap
					local STARTSFX = TYPE.grindstartSFX or DF.grindstartSFX
					S_StopSoundByID(s, STARTSFX)
					S_StartSoundAtVolume(s, STARTSFX, rvolume) --Slow start grinding SFX. Stacks with the optional fast SFX.
				--	GS.grindsfx2 = TYPE.SFXinit or DF.SFXinit //31
				end
			end
			if not (TYPE.landSFX==sfx_none) and GS.startSFX!="noland" --Clank! You hit a rail!
				local LANDSFX = DF.landSFX if (TYPE.landSFX!=nil) LANDSFX = TYPE.landSFX end
				S_StopSoundByID(s, LANDSFX) S_StartSound(s, LANDSFX)
			end
		end
		GS.startSFX = nil
	end
	if (GS.grinding!=2)
		if CONVEYOR
			if (GS.railspeed >= 0) and (CONVEYOR < 0)
			or (GS.railspeed < 0) and (CONVEYOR > 0)
				Rspeed = max(1, $-CONVEYOR)
			end	
		elseif (GRINDFLAGS & 131072) and (Rspeed < 12*s.scale) --We're on a cinematic rail and moving WAY too slowly!!
			GS.railspeed = ($<0) and -12*s.scale or 12*s.scale 
		end
		
		if not (GS.silentgrind or GS.silentgrind == 2) and not (LOOPFLAGS & 1048576)
			local RSPEED2 = abs(GS.railspeed or 0) --Without conveyor.
			GS.grindsfx,GS.grindsfx2 = max($-1,0), max($-1,0)
			
			if (abs(GS.railspeed+CONVEYOR) > 47*s.scale) --Speed lines!
			and not (STATS and STATS.NOWINDLINES==true)
			and not (RSTATS and RSTATS.VERTRAIL)
				RAIL.SpeedLines(s, GS, RAIL.ANG(rail)+GS.raildirection)
			end
			if (HANGRAIL==true) and not (TYPE.nohangrailSFX==true) 	--Play Hangrail SFX!
				if (GS.grindsfx < 2) --This one has set-in-stone sounds effects.
					S_StopSoundByID(s, sfx_gsral8)
					S_StartSoundAtVolume(s, sfx_gsral8, rvolume)
					GS.grindsfx = 31 //TYPE.SFXinit or DF.SFXinit //31				
				elseif not S_SoundPlaying(s, sfx_gsral8)
					S_StartSoundAtVolume(s, sfx_gsral8, rvolume)
				end
			elseif not ((GS.flipanim or 0) > 3) --Play Grinding SFX!
				if (TYPE.grindSFX!=sfx_none) and not GS.ropesnap
					local SFX = TYPE.grindSFX or DF.grindSFX
					local FAST = TYPE.fastgrindSFX or DF.fastgrindSFX
					if (GS.grindsfx < 2) --Grinding sounds play constantly.
						local PLAYSLOW = true
						if (RSPEED2 > 40*s.scale) 
							if (TYPE.fastgrindSFX!=sfx_none) --Fast grind sfx. Overlaps with slow.
								S_StartSoundAtVolume(s, FAST, rvolume/2)
								PLAYSLOW = (TYPE.nostackSFX==true) and true or false
								if FAST==sfx_gsralb and (RSPEED2 > 64*s.scale) and TYPE.grindstartSFX==sfx_gsrald
									S_StopSoundByID(s, sfx_gsrald)
									S_StartSoundAtVolume(s, sfx_gsrald, min(255, rvolume*3/2)) --Special case for Modern rails.
								end
							end
							GS.grindsfx2 = TYPE.fastSFXtimer or DF.fastSFXtimer  //26
							if (HANGRAIL!=true)
								local COLOR,COLORIZED = (TYPE.sparkscolor) or 0, (TYPE.sparkscolorized) and true or false
								RAIL.NewSparks(s, rail, COLOR, COLORIZED, (TYPE.sparkscale) or FRACUNIT/2, STATS)
							end
						end	
						if (PLAYSLOW!=true)
							S_StopSoundByID(s, SFX)
							S_StartSoundAtVolume(s, SFX, rvolume)
						end
						GS.grindsfx = TYPE.SFXtimer or DF.SFXtimer //71
					elseif not S_SoundPlaying(s, SFX)
						S_StartSoundAtVolume(s, SFX, rvolume)
					end
				end	
				if (RSPEED2 > 34*s.scale)
					local SCALE = (TYPE.sparkscale) or 34000
					if (RSPEED2 > 40*s.scale)	
						if (TYPE.fastgrindSFX!=sfx_none)
							local FAST = TYPE.fastgrindSFX or DF.fastgrindSFX
							if (GS.grindsfx2 < 2) --Fast grind sfx. Overlaps with slow.
								S_StopSoundByID(s, FAST)
								S_StartSoundAtVolume(s, FAST, rvolume/2)
								GS.grindsfx2 = TYPE.fastSFXtimer or DF.fastSFXtimer //26			
							elseif not S_SoundPlaying(s, FAST)
								S_StartSoundAtVolume(s, FAST, rvolume/2)
							end
						end
						if (RSPEED2 > 51*s.scale)
							SCALE = FixedDiv(FixedMul($, RSPEED2/s.scale), 51)
						end							
					end							
					if (HANGRAIL!=true) --Grindrail effects!
						local SCALE = (TYPE.sparkscale) or 34000
						if (RSPEED2 > 51*s.scale)
							SCALE = FixedDiv(FixedMul($, RSPEED2/s.scale), 51)
						end
						local COLORIZED = (TYPE.sparkscolorized) and true or false
						if (TYPE.nosparks!=true) --Some awesome sparks from MetalWario64!
							local COLOR = (TYPE.sparkscolor) or 0
							RAIL.NewSparks(s, rail, COLOR, COLORIZED, SCALE, STATS)	
						end
					end
				end
			end
			if not ((GS.flipanim or 0) > 4) and not (LOOPFLAGS & 1048576)
				if (RSPEED2 > 31*s.scale) or (GS.grinding < 9) --Always play these.
					if TYPE.spawndust
						RAIL.SpawnDust(s,TYPE.spawndust, SCALE, P_RandomRange(-24,24) or 1, HANGRAIL)
					end
					if TYPE.spawnbubbles
						local COLOR = (TYPE.spawnbubbles!=SKINCOLOR_BLUE) and TYPE.spawnfire or nil
						RAIL.SpawnBubbleDust(s, COLOR, SCALE, HANGRAIL)
					end
					if TYPE.spawnfire
						local COLOR = (TYPE.spawnfire!=SKINCOLOR_RED) and TYPE.spawnfire or nil
						RAIL.SpawnFlameDust(s, COLOR, SCALE, HANGRAIL)
					end
					if TYPE.spawnleaves and not (TYPE.windrail)
						RAIL.SpawnLeaves(s, TYPE.spawnleaves, SCALE, nil, P_RandomRange(40,40)*s.scale, HANGRAIL)
					end				
				end
				
				if (TYPE.speedtrail) and (RSPEED2 > TYPE.speedtrail*s.scale)
					GS.speedtrail = GS.speedtrail or true
					if TYPE.speedtrailcolor and type(TYPE.speedtrailcolor)=="number"
						GS.forcetrailcolor = TYPE.speedtrailcolor
					end
				end
				if (GS.silentgrind != 2) and ((TYPE.nosparks!=true) or (HANGRAIL==true)) --Continuous railgrinding sparks.
					local SPARKSPEED = min( max(4,(RSPEED2/(26*s.scale)) ), 1)
					RAIL.RailSparks(s, SPARKSPEED, //Gain 1 spark per 26 speed. Max 4, minimum 1.
					P_RandomRange(12,24), --RNG value
					P_RandomRange(-20,20), HANGRAIL) --RNG2 value
				end
			end
		end
		if GS.zboost 
			local MAX = DFSTATS.MAXSPEED*s.scale if STATS and STATS.MAXSPEED!=nil MAX = STATS.MAXSPEED*s.scale end
			if (GS.grinding > 4) or (abs(GS.railspeed) > MAX*3/4) --Initial landing slopeboost is only active for a short while.
				GS.zboost = nil
			end
		end
		if GS.IgnoreConveyor
			GS.IgnoreConveyor = $-1
		end
		if s.GScollide --We collided with a special object? Aight, then get this out of the way ASAP!
			RAIL.AttachedCollission(s, rail, GS)
			s.GScollide = nil
			if not GS or not GS.grinding or not (rail and rail.valid)
				return --Whatever we just touched took us away from the rail.
			end
		end		
	end
		
	--Always face rail direction or...?
	if not (p)
		if GS.willfacerail
			s.angle = GS.grinddirection
		end
	else --if p
		if (LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304)
		or LOOP and (LOOP.walljump==true or LOOP.anim and (LOOP.anim!=0))
			GS.camwait,GS.willfacerail,GS.turntoia = 0,0,0
			p.drawangle = RAILANG+GS.raildirection
		elseif GS.willfacerail--Players usually face their initial rail momentum angle. Other mobjs are different.
			GS.willfacerail = $-1 --Don't adjust angle for players until this is 0
		else  --Do some basic camera nonsense incase the player has the custom camera off.
			if GS.turntoia  --Gradually autoturn camera if the player isn't currently holding directions.
				GS.turntoia = $-1	
			end
			if GS.ropesnap and not ( (LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304) )
			and (GS.TWOD!=true) and not (CONVEYOR)
				if GS.forceangle==nil
					p.drawangle = s.angle --Face camera angle instead.
					GS.forceangle = s.angle
				end
			else	
				if not LOOP and not rail.GSVertRail
					if (p.cmd.buttons & BT_SPIN) --Face the camera?
						if not (GS.chasecam==false)
							s.angle = RAIL.ANG(rail)+GS.raildirection+(RAIL.GrindState(s, "hang") and (ANG1*3)+75000 or 0)
						end
					else
						local SIDE,FORWARD = GS.side or 0,GS.forward or 0
						if not (abs(SIDE) > 5) and (abs(FORWARD) > 45) --Center camera if holding forwards/backwards.
						or ((GS.railfalltimer or 0) > 15)
							if GS.camwait
								GS.camwait = $-1
							elseif not (GS.chasecam==false)
								local DESTANG = RAIL.ANG(rail)+GS.raildirection
								s.angle = DESTANG
							end
						else
							GS.camwait = 8
						end
					end
				end
				p.drawangle = RAILANG+GS.raildirection --Always instant adjust player drawangle.
			end
		end
	end
	
	--Boost to minimum speed if we're below it!f
	if GS.railminspeed and (Rspeed < GS.railminspeed*s.scale) and not CONVEYOR and not (LOOP and LOOP.walljump)
		GS.railspeed = (($<0) and -1 or 1)*GS.railminspeed*s.scale
	end
	
	--Now for the big deal. Our actual rail positioning! GS.railspeed will be negative if we're going backwards!
	if rail and rail.valid and not rail.GSVertRail 
		if not (p)
		
		or (p.powers[pw_justlaunched]!=2)
		and not (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_THOKKED))
		and not (p.powers[pw_carry] and p.powers[pw_carry]!=3888)
			if ((GS.grinding or 0) > 2) and not (s.eflags & MFE_SPRUNG) and not GS.leftrail
				s.momz = -((P_GetMobjGravity(s) or 0)*9/8) --Override regular game behaviour. We'll convert our rail momentum back later!
				if (GS.grinding%4==0) --Every so often, reallign incase of external momentum.
					local snapX, snapY = RAIL.GetRailXYDistFromOrigin(s, rail)
					if (JUMPFLAGS & (1048576|2097152)) and ((rail.GSrailXYlength or 0) < 2800<<16) and (GRINDFLAGS & 524288) 
					and not (CONVEYOR)
						snapX = rail.x-FixedMul(rail.GSrailXYlength/2, cos(RAILANG))
						snapY = rail.y-FixedMul(rail.GSrailXYlength/2, sin(RAILANG))
					end
					local snapZ = RAIL.GetRailZ(rail, s, 0, nil, {X=snapX, Y=snapY})
					P_MoveOrigin(s, snapX, snapY, snapZ)
				end
				P_InstaThrust(s, p and p.drawangle or rail and RAIL.ANG(rail), 180)
			end
		end
	end
	
	if GS.leftrail return end --
		
	local m = rail.GSnextrail --"Next" is a lua function, so uh...m it is.
	if m == nil RAIL.print("\x85"+"Fatal error, no next rail!", 25) RAIL.ExitRail(s) return end
	local prev = rail.GSprevrail --"Prev" can be optional, but nextrail is mandatory!	
	
	if GS.LastGrindZ==nil
	or GS.grinding==2 and (GS.LastGrindZ!=s.z) and not GS.exittic --Sometimes we gotta setup!
		if RAIL.ValidGrind(GS, rail, CONVEYOR) and not (rail.GSVertRail) 
			s.z = RAIL.GetRailZ(rail, s, 0) --Make we've set it up properly!
		end
		GS.LastGrindZ = s.z
	end
	if not GS.exittic --Keep setting this again and again.
		if GS.LastGrindZ!=nil GS.LastGrindZ2 = GS.LastGrindZ end --A secondary one to mitigate jank.
		GS.LastGrindZ = s.z --Remember where we were the tic BEFORE we moved!	
	end
	
	if RSTATS==nil or RSTATS.RAIL!=rail		
		local OLDFALLING = 0
		if RSTATS and RSTATS.RAIL and RSTATS.RAIL.valid 
			OLDFALLING = RSTATS.falling or 0 --Put the sparks and sounds away?	
			if ((rail.GShangrail!=RSTATS.GShangrail) or (rail.GSVertRail!=RSTATS.GSVertRail)) and p
				if GS.railtilt GS.railtilt = $/4 end
				GS.perfectcorner = max(GS.perfectcorner or 0, 5)
			end
			if ((rail.GStype!=RSTATS.RAIL.GStype) or (LOOPFLAGS & 1048576)) and (RSTATS.RAIL.GStype!=nil) 
				local OLDTYPE = RAILTYPE[RSTATS.RAIL.GStype]
				if (OLDTYPE)
					if OLDTYPE.nosparks==true or (LOOPFLAGS & 1048576)
						GS.railsparks = nil 
					end
					if (OLDTYPE.grindSFX!=TYPE.grindSFX) or (OLDTYPE.fastgrindSFX!=TYPE.fastgrindSFX) or (LOOPFLAGS & 1048576)
						GS.startSFX,GS.grindsfx,GS.grindsfx2 = "noland",0,0
						local SOUNDSTOP = {OLDTYPE.grindstartSFX or sfx_gsral1, OLDTYPE.grindSFX or sfx_gsral3, 
						OLDTYPE.faststartSFX or sfx_gsral2, OLDTYPE.fastgrindSFX  or sfx_gsral4, sfx_gsral8}
						for _,i in pairs(SOUNDSTOP) if i S_StopSoundByID(s, i) end end --Stop some sounds.					
					end
				end
			end
		end
		GS.MyRailStats = {INIT = 0, RAIL = rail, VERTRAIL = 0, LASTZ = GS.LastGrindZ, INITZ = GS.LastGrindZ, ZSPEED = 0, falling = OLDFALLING}
		if (rail.GSgrindflags & 8388608) and rail.spawnpoint 
			if rail.spawnpoint.tag==0
				rail.spawnpoint.tag = 42763 --Hit a random-feeling number and pray it's not needed.
				RAIL.error("\x85"+"ERROR:\x80 rail[\x82"+s.GSrailnum+"\x85] has a linedef trigger flag"+ 
				", yet no tag assigned!"+"\n"+"Spawned\x81 debug TOAD\x85 on location.")	
			elseif type(rail.spawnpoint.tag)=="number"
				P_LinedefExecute(rail.spawnpoint.tag, s, (s.subsector) and s.subsector.sector or nil)
				if consoleplayer==displayplayer and consoleplayer and consoleplayer.GSRO and consoleplayer.GSRO.devmode
					CONS_Printf(consoleplayer, "Activating\x87 Linedef Trigger"+"\x80"+"[tag:\x88"+rail.spawnpoint.tag+"\x80"+
					"] - Called by Railnum[\x82"+rail.GSrailnum+"\x80"+"]")
				end
			end	
		end
	else
		RSTATS.INIT = $+1 --How long we've been on this rail.
		if RSTATS.VERTRAIL
			RSTATS.VERTRAIL = max($-1,0)
			if not RSTATS.VERTRAIL
				RSTATS.ZSPEED, GS.MyRailStats.VERTRAIL = 0,0
			end
		end
		local ASPEED = abs((GS.railspeed or 0)+CONVEYOR)
		local SPEEDMET = ((LOOP) and LOOP.speedneeded or 0)*s.scale
		if SPEEDMET and (ASPEED < SPEEDMET) --Too slow to stay on!
		and (RSTATS.VERTRAIL or (LOOPFLAGS & 131072))
			if (GRINDFLAGS & 524288)
				RSTATS.falling = max($-2,0)
			else
				local ADD = (ASPEED < (SPEEDMET/3)) and 4 or (ASPEED < (SPEEDMET/2)) and 3 or (ASPEED < (SPEEDMET*2/3)) and 2 or 1
				RSTATS.falling = RSTATS.falling and $+ADD or ADD
				if (RSTATS.falling > 15)
					RAIL.ExitRail(s, true)
					GS.cannotgrind = max($+7, 24) return --REVERSE, CONVEYOR, GAINING --Early quit!
				end
			end
		elseif RSTATS.falling 
			RSTATS.falling = min(9, $-1)
		end
	end
	if (GRINDFLAGS & 524288) --Zero momentum flag.
		if CONVEYOR
			GS.railspeed = (CONVEYOR<0) and -1 or 1
		else
			GS.railspeed = ($<0) and -1 or 1
			if GS.homingANG!=nil
				if (GS.grinding < 4) and not (LOOPFLAGS & (524288|262144|4194304))
					RAIL.MobjToRailAngle(s, rail, GS.homingANG, true)
					if not (GS.chasecam==false)
						p.cmd.angleturn = GS.homingANG/FRACUNIT
						s.angle = GS.homingANG
					end
					p.drawangle = GS.homingANG
					if GS.railcam and GS.railcam.valid
						GS.railcam.angle = GS.homingANG
					end				
				else
					GS.homingANG = nil
				end
			end			
		end
	end
	local RAILLIMIT = rail.GSrailXYlength or 0
	
	if (rail.GStouchflags & 65536) and p --Player not allowed to interact!
	or (rail.GSdisabled) --The rail is disabled?!
		GS.FORCEKICK = true
	elseif (GRINDFLAGS & 524288) and not (CONVEYOR) --A true Zero Speed rail?!
		RAILLIMIT = max($, 64<<16)
		if ((GS.grinding or 0)<3)
			GS.railprogress = (rail.GSrailXYlength or 2)/2
		end
	elseif rail.GSrailmaxspeed and (abs(GS.railspeed) > rail.GSrailmaxspeed*s.scale) and (s.skin!="sms")
		GS.railspeed = (($<0) and -rail.GSrailmaxspeed or rail.GSrailmaxspeed)*s.scale
	end
	
	--Do Vertical Grinding?
	local STARTZ,DESTZ,ZLIMIT,NEWRAIL = RAIL.VertRail(rail, "check")
	if (ZLIMIT!=nil or NEWRAIL!=nil) and not ((GRINDFLAGS & 524288) and not (CONVEYOR))
		local VERTICALRAIL = RAIL.VerticalGrind(p, s, GS, CONVEYOR, REVERSE, STARTZ, DESTZ, ZLIMIT)
		if (VERTICALRAIL!=nil)
			return REVERSE, CONVEYOR, GAINING, "VERTICALRAIL" --
		end
	end
	local PROGRESS, LIMITTWO = RAIL.RAILPROGRESS(s, GS, rail)
	GS.railprogress = PROGRESS		
	--DOGRIND --DORAIL
	local ADDSPEED = min(max(2, LIMITTWO-1), GS.railspeed)
	LIMITTWO = max((30<<16)+(1<<15), LIMITTWO or 0)	
	if (GRINDFLAGS & 524288) 
		ADDSPEED = 0
		if ((GS.grinding or 0)<3) and not (CONVEYOR)
			GS.railprogress = (rail.GSrailXYlength or 2)/2
			PROGRESS = GS.railprogress
		end	
	elseif rail.GSrailspeed and ADDSPEED
		ADDSPEED = FixedDiv(FixedMul($, rail.GSrailspeed), 100)
	end
	if GS.EaseInSpeed
		ADDSPEED = FixedDiv(FixedMul($, 100), GS.EaseInSpeed)
		GS.EaseInSpeed = (GS.grinding==2) and max(101, $*3/4) or nil
	end
	local MAXNUM = (GS.exittic) and 16 or 1
	
	for i = 1,MAXNUM
		if GS.leftrail return end --ABORT! You JUST left the rail already!
		
		if (i!=1) --Take two!
			m = rail.GSnextrail --"Next" is a lua function, so uh...m it is.
			prev = rail.GSprevrail --This one can be optional, but nextrail is mandatory.
			if m == nil RAIL.print("\x85"+"Fatal error, no next rail!", 25) RAIL.ExitRail(s) return end
		end
		local RAILANG = RAIL.ANG(rail)
		local newrail = prev	
		local NONEXT = (not (newrail and newrail.valid) or (newrail.flags2 & MF2_DONTDRAW) or newrail.GSdisabled) and true or false
		if (GS.railspeed+CONVEYOR >= 0) newrail = rail.GSnextrail end

		if (i==1) or not (GS.railprogress > RAILLIMIT) --Grind towards our DESTINY!	
			local DISTMOVE = ADDSPEED
			if CONVEYOR and not (GS.IgnoreConveyor)
				local CONV = CONVEYOR
				if ( abs(CONVEYOR) > (RAILLIMIT/2) )
					CONV = ($<0) and -RAILLIMIT/2 or RAILLIMIT/2 --Never shoot players past a whole rail in 1 tic!
				end
				if ((GS.flipanim or 0) > 1) and ((GS.railspeed >= 0) and (CONVEYOR < 0) or (GS.railspeed < 0) and (CONVEYOR > 0))
				and not (STATS and STATS.HEROESTWIST==1) --Don't fight conveyor if your twist is cosmetic only.
					CONV = ((GS.flipanim > 4) and (GS.flipanim < 8)) and $/9 
					or ((GS.flipanim > 9) or (GS.flipanim < 2)) and $/2 or $/5
				elseif (GS.grinding < 4) --Start up a little slower!
					CONV = (GS.grinding < 3) and $/3 or $/2
				end
				DISTMOVE = $+CONV	
			elseif (LOOPFLAGS & 131072) and (abs(DISTMOVE) > 60*s.scale)
				DISTMOVE = (abs(DISTMOVE) > 84*s.scale) and FixedDiv(FixedMul($, 7), 8) or FixedDiv(FixedMul($, 8), 9)
			end
			local RX,RY,RZ = s.x-FixedMul(DISTMOVE, cos(RAILANG)), s.y-FixedMul(DISTMOVE, sin(RAILANG)), s.z
			RZ = RAIL.GetRailZ(rail, s, -ADDSPEED-CONVEYOR, nil, {X=RX, Y=RY})
			if RSTATS and RSTATS.INIT
				RZ = min( P_CeilingzAtPos(RX, RY, RZ, 0), max(P_FloorzAtPos(RX, RY, RZ, 0), RZ) )
			end
		--	if not (s.eflags & MFE_VERTICALFLIP) and (s.z < RZ) or (s.eflags & MFE_VERTICALFLIP) and (s.z > RZ) 
				s.z = RZ	
		--	end
			if p P_TryMove(s, RX, RY, true) end --To better collide with items on rails.
			P_MoveOrigin(s, RX, RY, RZ) --print("\x85"+"!!!!!!!!!!!!!!!!!!!"+leveltime)
			GS.railprogress = RAIL.RAILPROGRESS(s, GS, rail)
		end
		
		--Swap to the next rail if there is one!
		if (GS.railprogress+((NONEXT==true) and abs(GS.railspeed or 0) or 0) > RAILLIMIT)
		or GS.FORCEKICK
			if (newrail and newrail.valid and newrail.GSnextrail)
			and not (newrail.flags2 & MF2_DONTDRAW) --Invisible rails set their sprites to SPR_NULL! They DON'T use MF2_DONTDRAW!
			and not ((not newrail.GSrailXYlength) and newrail.GSforceUDMFang!=nil)
			and not (GS.FORCEKICK) --Not allowed to interact!
				local NEWRAILANG = RAIL.ANG(newrail)
				local OLDSLOPE, OLDANG = rail.GSrailZDelta or 0, s.angle
			--	local OLDNUM = GS.myrail.GSrailnum --For DEBUG
				
				GS.prevrailang = GS.grinddirection
				GS.turntoia = 10 --We just turned. Turn camera/object to angle?
				if p
					GS.ropesnap = nil
					OLDANG = p.drawangle
					p.drawangle = NEWRAILANG+GS.raildirection
					if i==1 and not (RSTATS and RSTATS.VERTRAIL) and (GS.TWOD!=true) and not (GS.myrail.GSVertRail)
						local ANG,PLUS = RAIL.AngleDifference(OLDANG, p.drawangle, 2, ANG1*89, 2)
						if (ANG > ANG1*7) --Players can lose balance overtime.
							GS.railsparks = nil
							local SPEED,MINTIME,CONSEC = abs(ADDSPEED or 0),18,0
							if GS.ANGswap
								MINTIME = max($, GS.ANGswap.timer or 0)
								if (GS.ANGswap.PLUS != PLUS)
									MINTIME = $+12
									ANG = min(ANG1*89, $+ANGLE_22h) --Become unstable easier from crazy swerves.
								end
								if (GS.ANGswap.consec!=nil)
									CONSEC = GS.ANGswap.consec+1
								end
							end
							if (SPEED < 32*s.scale) --Instability becomes somewhat insignicant when moving slowly.
								ANG = ($/7)*max(1, min(6, SPEED/(s.scale*3)) )
							end
							GS.ANGswap = {timer = min(64, max(MINTIME, SPEED/(s.scale*2)) ),
							ANG=ANG, PLUS=PLUS, init = 0, consec = CONSEC}
						else
							if GS.railcam and GS.railcam.valid
								GS.railcam.dimROD = {timer = 2, div = 2, init = 0}
							end 
							if (ANG > ANG1/20)
								local CAMADD = GS.camadd or 0
								GS.FreeAdd = 11
								if GS.crouchgrind and GS.crouchbutton
								or GS.badbalance==true and (abs(GS.railtilt) > 320)
								or (STATS and STATS.MUSTBALANCE==true)
								or (abs(GS.railtilt) < 182)
									if (GS.grinding > 6) and not (GS.perfectcorner)
									and not (GS.cantcrouch) and not (LOOP and (LOOP.anim!=0))
									and not (RSTATS and RSTATS.VERTRAIL) and not (p.powers[pw_nocontrol])
										local ADDTILT = -min(99, ANG/(ANG1/33))
										if ( (abs(GS.railtilt) < 182) 
										and not (GS.crouchgrind and GS.crouchbutton) and not (STATS and STATS.MUSTBALANCE==true)
										) or GS.flipanim
											ADDTILT = $*4/3 --More balance loss!
										elseif (abs(GS.railtilt) > 400)
											ADDTILT = $*2/3 --They're nearly falling already, take it easy!
										end
										if STATS and STATS.AUTOBALANCE==true
											ADDTILT = $/3
										end
									/*	local SIDE = (p.cmd.sidemove or 0)*GS.invertinput --Holding same direction as turn?
										if (ADDTILT > 0) and (SIDE > 7) or (ADDTILT < 0) and (SIDE < -7)
											GS.railtilt = $*8/9  --Stabilize instead.
											if not (GS.perfectcorner) GS.perfectcorner = 1 end  print("\x83"+"Stabilize:"+GS.railtilt)
										end */
										GS.railtilt = GS.railtilt+(ADDTILT*PLUS)
									end
								end
							end
						end
					end
				end
				
				--NOW SWAP TO THE NEW RAIL!
				if newrail.GSVertRail --Vertical rail swapping nonsense! --RAIL.VertRail(newrail, "check") or 
					local OGZ = s.z
					local SETZ = s.z
					local NEXT,OLD = newrail or GS.myrail, GS.myrail
					if (GS.railspeed+CONVEYOR >= 0)
						SETZ = RAIL.SetVertZ(OLD, SPEED)
						if OLD.GSnextrail NEXT = OLD.GSnextrail end
					elseif OLD.GSnextrail
						SETZ = RAIL.SetVertZ(OLD.GSnextrail, SPEED)
						NEXT = OLD
					end	
					if RSTATS
						RSTATS.ZSPEED = newrail.GSVertRail
						RSTATS.VERTRAIL = 2
						RSTATS.INIT = 0
						RSTATS.LASTZ = OGZ
					end	
					P_TryMove(s, NEXT.x, NEXT.y, true) 
					if newrail.GSVertRailBotZ!=nil
						SETZ = max(newrail.GSVertRailBotZ, $)
					end
					if newrail.GSVertRailTopZ!=nil
						SETZ = min(newrail.GSVertRailTopZ, $)
					end					
					s.z = SETZ
					GS.railprogress,GS.railsparks = 0, nil
					if (abs(GS.railspeed or 0) > 45*s.scale)
						GS.railspeed = FixedDiv(FixedMul($, 20), 21)
					end
					P_TryMove(s, NEXT.x, NEXT.y, true) 
					ADDSPEED = $/3
					GS.myrail = newrail break --
					
				elseif (GS.railspeed+CONVEYOR >= 0) --Move onto the next rail!
				
					local diff = min(LIMITTWO-1, FixedHypot(s.x-newrail.x, s.y-newrail.y))
					local NX,NY = newrail.x-FixedMul(diff, cos(NEWRAILANG)), newrail.y-FixedMul(diff, sin(NEWRAILANG))
					local NZ = RAIL.GetRailZ(newrail, s, 0, nil, {X=NX, Y=NY})
					P_MoveOrigin(s, newrail.x-FixedMul(diff, cos(NEWRAILANG)), newrail.y-FixedMul(diff, sin(NEWRAILANG)), NZ)
					ADDSPEED = min($/2, diff)	
					
				else --Formula is different for previous rail.
					local diff = min(LIMITTWO-1, FixedHypot(s.x-rail.x, s.y-rail.y))	--Use rail instead of newrail dist.
					local NX,NY = rail.x+FixedMul(diff, cos(NEWRAILANG)), rail.y+FixedMul(diff, sin(NEWRAILANG))
					local NZ = RAIL.GetRailZ(newrail, s, 0, nil, {X=NX, Y=NY})
					P_MoveOrigin(s, rail.x+FixedMul(diff, cos(NEWRAILANG)), rail.y+FixedMul(diff, sin(NEWRAILANG)), NZ)		
					ADDSPEED = min($/2, diff)
				end	
				GS.myrail = newrail --Attach to the new rail.
				if RSTATS and RSTATS.INIT
					RSTATS.INIT = 0
				end
				
				--Check if we're not BARELY missing a spring/booster on the rail's edge...
				if (RAIL.ScanCollide(s, GS, false, (GS.railspeed or 0)+CONVEYOR)==true)
					return REVERSE, CONVEYOR, GAINING --
				end
				
				local NEWLIMIT = newrail.GSrailXYlength or 1
				--Either we're EXTREMELY fast or this rail is tiny. 
				if (GS.railprogress > NEWLIMIT)				
					
			--	or ((m.GSconveyor or 0) > 0) and not (m.GSnextrail and m.GSnextrail.valid)
			--	or ((m.GSconveyor or 0) < 0) and not (m.GSprevrail and m.GSprevrail.valid)
				
				--	print("\x85"+"[SWITCH] s.z:\x80"+s.z/FRACUNIT+"\x85 Progress:\x80"+GS.railprogress/FRACUNIT)
					if (GS.railspeed+CONVEYOR >= 0) and newrail.GSnextrail
					and newrail.GSnextrail.valid and not (newrail.GSnextrail.flags2 & MF2_DONTDRAW)
					
					or (GS.railspeed+CONVEYOR < 0) and newrail.GSprevrail
					and newrail.GSprevrail.valid and not (newrail.GSprevrail.flags2 & MF2_DONTDRAW)
						break --This skip business is only for the last one!
					end
				--	print("\x83"+"[SWITCHOVERFLOW] s.z:\x80"+s.z/FRACUNIT+"\x83 Progress:\x80"+GS.railprogress/FRACUNIT)
					
					rail = GS.myrail
					GS.railprogress = max(0, min((rail.GSrailXYlength or 0)-1, $-NEWLIMIT))
				--	print("\x85"+"["+OLDNUM+" > HO-SWAP "+GS.myrail.GSrailnum+"]")	
				--	print("Nope, do it. progress:\x82 "+GS.railprogress>>16+"\x80 newrail.length:\x82"+newrail.GSrailXYlength>>16)
					continue --
			--	else
				--	print("\x83"+"[SWITCH] s.z:\x80"+s.z/FRACUNIT+"\x83 Progress:\x80"+GS.railprogress/FRACUNIT)
				--	print("\x83"+"["+OLDNUM+" > H-SWAP "+GS.myrail.GSrailnum+"]")
				end			
			else	
				if not (GS.FORCEKICK)
					if (GRINDFLAGS & 524288) and not (CONVEYOR)
						GS.railprogress = 1 break --No falling off in this case!
					elseif (RAIL.ScanCollide(s, GS, true, (GS.railspeed or 0)+CONVEYOR)==true)
						break -- --Check if we're not BARELY missing a spring/booster on the rail's edge...
					end
				
					if not (GS.exittic) and ( (GS.railprogress > RAILLIMIT) or ((GS.grinding or 0) < 3) ) and not (CONVEYOR)
						local diff = abs(GS.railspeed+CONVEYOR)
						local NX,NY = s.x-FixedMul(diff, cos(RAILANG)), s.y-FixedMul(diff, sin(RAILANG))
						local NZ = RAIL.GetRailZ(rail, s, 0, nil, {X=NX, Y=NY})		
						P_MoveOrigin(s, NX,NY, NZ)
						GS.exittic = true continue --
					end
					
					if (rail.GSVertRail) or (RSTATS and RSTATS.VERTRAIL) break end --VerticalGrind takes over!
					if (GS.railspeed+CONVEYOR >= 0) --First. Move to the exact endpoint of the rail.
						if not (CONVEYOR) or (FixedHypot(FixedHypot(m.x-s.x, m.y-s.y), m.z-s.z) < 160*s.scale)
							P_MoveOrigin(s, m.x, m.y, m.z)
						end
					elseif not (CONVEYOR) or (FixedHypot(FixedHypot(rail.x-s.x, rail.y-s.y), rail.z-s.z) < 160*s.scale)				
						P_MoveOrigin(s, rail.x, rail.y, rail.z)
					end
				end
				RAIL.ExitRail(s, true) -- --No rail. Convert momentum and go!
			end	
		end	
		break --
	end 
	return REVERSE, CONVEYOR, GAINING --We've made it, bring the good news back to the PlayerThink!
end

--After JUST exiting a rail.
RAIL.LEFTRAIL = function(p, s, GS)
	GS.leftrail = min(3, max($-1,0)) if not (s.health) return end --
	if GS.LTAB
		if GS.LTAB.doflip and not GS.leftrail
			P_SpawnGhostMobj(s) --Flip jump!
			if (s.eflags & MFE_VERTICALFLIP)
				s.momz = $-(10*s.scale)
				s.flags2 = $ & ~MF2_OBJECTFLIP
				s.eflags = $ & ~MFE_VERTICALFLIP
				P_PlayerZMovement(s)
			else
				s.momz = $+(10*s.scale)
				P_PlayerZMovement(s)
				s.flags2 = $|MF2_OBJECTFLIP
				s.eflags = $|MFE_VERTICALFLIP
			end
			S_StartSoundAtVolume(s, sfx_gravch, 160)
			GS.LTAB.timer = max(GS.LTAB.timer or 0, 11)
			GS.LTAB.doflip = false
		end	
		if (GS.LTAB.init <= 1)
			if GS.LTAB.dojumpboost
				if GS.LTAB.dojumpboost=="2D"
					if (p.cmd.sidemove)
						local ANG = (p.cmd.sidemove < 0) and ANGLE_180 or 0 --0 is right. 180 is left.
						P_Thrust(s, ANG, abs(p.cmd.sidemove)*(s.scale/2))
					end
					GS.LTAB.dojumpboost = nil
				elseif GS.LTAB.dojumpboost=="3D"
					if (p.cmd.forwardmove or p.cmd.sidemove)
						local ANG = p.cmd.angleturn<<16
						if p.awayviewcam and (p.awayviewcam==GS.railcam) and not (GS.nocam)
						and p.awayviewtics and GS.railcam.valid
							ANG = GS.railcam.angle
						end
						ANG = $+(R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16)))
						P_Thrust(s, ANG, min(50, abs(p.cmd.forwardmove)+abs(p.cmd.sidemove))*(s.scale/2))
					end
				end
				GS.LTAB.dojumpboost = nil	
			elseif GS.LTAB.snappedtofloor and P_IsObjectOnGround(s) --and (p.speed < 5*s.scale)
			and not (p.pflags & (PF_SPINNING|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_STARTDASH|PF_SLIDING))
			and not (P_PlayerInPain(p)) and not p.powers[pw_carry] and not (GS.LTAB.GetUpAnimANG!=nil)
			--	print("END SPEED:\x82 "+GS.LTAB.thrust or 0+" p.speed:"+p.speed/FRACUNIT+" abs(s.momx+y):\x82"+((abs(s.momx)+abs(s.momy))/FRACUNIT))
				if GS.LTAB.thrust
					if GS.LTAB.MOMANG!=nil
						P_InstaThrust(s, GS.LTAB.MOMANG, GS.LTAB.thrust)
					end
				end
				if GS.LTAB.laststate and s.state!=GS.LTAB.laststate
					s.state = GS.LTAB.laststate
				end
				if s.momz==0
					s.momz = -P_MobjFlip(s)
				end
				s.eflags = $|MFE_JUSTHITFLOOR
			end
		end
	end
	
	local TRIEDJUMP = false							
	if (GS.leftrail < 4)
		s.friction = max($, FRACUNIT)
		if not (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_THOKKED|PF_STARTDASH|PF_FULLSTASIS|PF_JUMPSTASIS)) 
		and (p.panim==PA_FALL or p.panim==PA_ROLL or ((p.panim>=PA_IDLE) and (p.panim<=PA_DASH)) )
		and not (s.eflags & MFE_SPRUNG) and not p.exiting
		and not (p.powers[pw_carry] and (p.powers[pw_carry]!=3888))
			if (p.cmd.buttons & BT_JUMP) and (GS.backupjump < 33) --Buffering stuff!
			and not GS.railswitching and not GS.JumpRamp and not (GS.LTAB and GS.LTAB.nojumpbuffer)
				p.pflags = $|PF_JUMPDOWN --Buffer a jump?
				P_DoJump(p, true)
				TRIEDJUMP = true
			elseif not (s.momz*P_MobjFlip(s) > 10*s.scale)
				if not p.cmd.forwardmove and not p.cmd.sidemove
					p.cmd.forwardmove = 1
				end
				local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
				if (abs(s.z-FLOORZ) < 60*s.scale) and GS.LTAB and GS.LTAB.snappedtofloor
					s.z = FLOORZ
					P_SetObjectMomZ(s, -8<<16, true)
					if (GS.backupspin < 28) and (p.cmd.buttons & BT_SPIN) and not (p.pflags & PF_SPINNING) 
					and not (GS.LastRail and GS.LastRail.GSnorolling)
						p.pflags = $ & ~PF_USEDOWN --Buffer a spin input?
						if p.yusonictable p.yusonictable.spin = 0 end
					end
				end
			end
		end	
	end	
	if TRIEDJUMP==false and not (GS.LTAB and GS.LTAB.nojumpbuffer)
		local LOOP = RAIL.LearnLoopType(GS.LastRail or GS.myrail)	
		if LOOP and (LOOP.endwithspin!=false) and not (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_JUMPED)) 
			if (p.cmd.buttons & BT_SPIN) or GS.spinstate==true
				local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
				if (abs(s.z-FLOORZ) < 82*s.scale) or LOOP.endwithspin=="always"
					if p.charability2==CA2_SPINDASH or LOOP.endwithspin=="force" or LOOP.endwithspin=="always"
						if not (GS.LastRail and GS.LastRail.GSnorolling)
							p.cmd.buttons = $|BT_SPIN
							p.pflags = $|PF_SPINNING
							s.state = S_PLAY_ROLL
						end
					end
				end
			end
		end
	end	
end